/**
 * @author Luc Yriarte
 * 
 * Licence : BSD http://www.opensource.org/licenses/bsd-license.php
 *  
 */
package basicSim;

import java.awt.Color;
import java.awt.Graphics;

/**
 * Object3D : Base class for all 3D Objects.
 * 
 * Left handed coordinates.
 * Every 3D object has its own referencial as a transormation matrix relative to its
 * parent in the composition hierarchy.
 */
public class Object3D {
	/** Draw an object's edges */
	public static int DRAW_WIREFRAME = 0;
	/** Draw an object's facets */
	public static int DRAW_FACETS = 1; 
	public static int BRIGHT_SCALE = 255;
	public static int BRIGHT_SCENE = 0;
	public static int BRIGHT_OBJECT = 1;
	public static int BRIGHT_MODE = BRIGHT_OBJECT;
	public static int DRAW_DEFAULT = DRAW_FACETS;
	/** Number of segments to approximate a circle */
	public static int N_DEFAULT = 12; 

	public float degRotX, degRotY, degRotZ, degRot0X, degRot0Y, degRot0Z;
	public double trnX, trnY, trnZ, trn0X, trn0Y, trn0Z;

	public Color color;
	public int drawMode = DRAW_DEFAULT;
	public float brightIncrement = 0;
	public float brightIncLocal = 0;
	public int brightDelta = 0;

	Matrix position, orientation, transformation;
	protected Matrix updatedTransform;
	protected Mesh mesh;
	
	protected int nChild = 0, nFacet = 0, nFacetTotal = 0;
	protected Object3D[] child = null;
	protected Object3D parent = null;
	
	protected int[][] wireframe = null;
	protected PolygonEdgeIndexes[] facet = null;
	protected PolygonEdgeIndexes[] facetTotal = null;
	
	public Object3D() {
		color = null;
		mesh = new Mesh();
		degRotX = degRotY = degRotZ = degRot0X = degRot0Y = degRot0Z = 0;
		trnX = trnY = trnZ = trn0X = trn0Y = trn0Z = 0;
		position = new Matrix3D();
		orientation = new Matrix3D();
		transformation = new Matrix3D();
		updatedTransform = null;
		nFacet = 0;
		facet = null;
	}

	public Object3D reset() {
		position = new Matrix3D();
		orientation = new Matrix3D();
		if (degRot0X != 0)
			mesh.transformThis(Matrix3D.rotationX(degRot0X * 2 * Math.PI / 360));
		if (degRot0Y != 0)
			mesh.transformThis(Matrix3D.rotationY(degRot0Y * 2 * Math.PI / 360));
		if (degRot0Z != 0)
			mesh.transformThis(Matrix3D.rotationZ(degRot0Z * 2 * Math.PI / 360));
		if (trn0X != 0 || trn0Y != 0 || trn0Z != 0)
			mesh.transformThis(Matrix3D.translation(trn0X, trn0Y, trn0Z));
		if (degRotX != 0)
			orientation = orientation.mul(Matrix3D.rotationX(degRotX * 2
					* Math.PI / 360));
		if (degRotY != 0)
			orientation = orientation.mul(Matrix3D.rotationY(degRotY * 2
					* Math.PI / 360));
		if (degRotZ != 0)
			orientation = orientation.mul(Matrix3D.rotationZ(degRotZ * 2
					* Math.PI / 360));
		if (trnX != 0 || trnY != 0 || trnZ != 0)
			position = position.mul(Matrix3D.translation(trnX, trnY, trnZ));
		transformation = position.mul(orientation);
		updatedTransform = null;
		if (color == null) {
			if (parent != null)
				color = parent.color;
			else
				color = new Color(0);
		}
		brightIncLocal = nFacet != 0 ? BRIGHT_SCALE / nFacet : 0;
		nFacetTotal = nFacet;
		for (int i = 0; i < nChild; i++) {
			child[i].reset();
			nFacetTotal += child[i].nFacetTotal;
		}
		brightIncrement = nFacetTotal != 0 ? BRIGHT_SCALE / nFacetTotal : 0;
//System.out.println(this.getClass().toString() + " has " + nChild + "children and " + nFacetTotal + " facets");
		if (parent == null && nFacetTotal > 0) {
			facetTotal = new PolygonEdgeIndexes[nFacetTotal];
			buildFacetList(facetTotal, 0);
		}
		if (nFacetTotal == 0 && mesh != null)
			drawMode = DRAW_WIREFRAME;
		return this;
	}

	public String toString() {
		return strObject(0);
	}

	public String strObject(int nTab) {
		String str = "", strTab = "";
		for (int i = 0; i < nTab; i++)
			strTab += "\t";
		str += strTab + "{\n";
		str += strCoord(nTab);
		str += strColor(nTab);
		str += strChild(nTab);
		str += strTab + "}\n";
		return str;
	}

	public String strChild(int nTab) {
		String str = "";
		for (int i = 0; i < nChild; i++)
			str += child[i].strObject(nTab + 1);
		return str;
	}

	public String strCoord(int nTab) {
		String str = "", strTab = "";
		for (int i = 0; i < nTab; i++)
			strTab += "\t";
		if (trn0X != 0 || trn0Y != 0 || trn0Z != 0)
			str += strTab + "trn0(" + new Double(trn0X).toString() + ","
					+ new Double(trn0Y).toString() + ","
					+ new Double(trn0Z).toString() + ")\n";
		if (degRot0X != 0 || degRot0Y != 0 || degRot0Z != 0)
			str += strTab + "rot0(" + new Float(degRot0X).toString() + ","
					+ new Float(degRot0Y).toString() + ","
					+ new Float(degRot0Z).toString() + ")\n";
		if (trnX != 0 || trnY != 0 || trnZ != 0)
			str += strTab + "trn(" + new Double(trnX).toString() + ","
					+ new Double(trnY).toString() + ","
					+ new Double(trnZ).toString() + ")\n";
		if (degRotX != 0 || degRotY != 0 || degRotZ != 0)
			str += strTab + "rot(" + new Float(degRotX).toString() + ","
					+ new Float(degRotY).toString() + ","
					+ new Float(degRotZ).toString() + ")\n";
		return str;
	}

	public String strColor(int nTab) {
		String str = "", strTab = "";
		if (color == null || (parent != null && parent.color == color))
			return str;
		for (int i = 0; i < nTab; i++)
			strTab += "\t";
		str += strTab + "color(" + new Integer(color.getRed()).toString() + ","
				+ new Integer(color.getGreen()).toString() + ","
				+ new Integer(color.getBlue()).toString() + ")\n";
		return str;
	}

	public Matrix getOrientation() {
		return orientation;
	}

	public Object3D setOrientation(Matrix orientation) {
		this.orientation = orientation;
		transformation = position.mul(orientation);
		updatedTransform = null;
		return this;
	}

	public Matrix getPosition() {
		return position;
	}

	public Object3D setPosition(Matrix position) {
		this.position = position;
		transformation = position.mul(orientation);
		updatedTransform = null;
		return this;
	}

	public Mesh getMesh() {
		return mesh;
	}

	public Object3D setMesh(Mesh mesh) {
		this.mesh = mesh;
		return this;
	}

	public Object3D transform(Matrix trans) {
		transformation = transformation.mul(trans);
		updatedTransform = null;
		return this;
	}

	public Object3D transformMesh(Matrix trans) {
		mesh.transformThis(trans);
		return this;
	}

	public int addChild(Object3D obj) {
		int i;
		Object3D[] prevChild = child;
		child = new Object3D[nChild + 1];
		for (i = 0; i < nChild; i++) {
			child[i] = prevChild[i];
		}
		child[nChild] = obj;
		obj.parent = this;
		if (obj.color == null)
			obj.color = this.color;
		nChild++;
		return nChild;
	}

	public Object3D getChild(int i) {
		if (child == null)
			return null;
		return child[i];
	}

	public int buildFacetList(PolygonEdgeIndexes[] facetList, int startIndex) {
		int i;
		for (i = 0; i < nFacet; i++) {
			facetList[startIndex++] = facet[i];
		}
		for (i = 0; i < nChild; i++) {
			startIndex = child[i].buildFacetList(facetList, startIndex);
		}
		return startIndex;
	}

	public void sortFacetTotal() {
		int i, n;
		PolygonEdgeIndexes tmpFacet;
		n = nFacetTotal;
		do {
			tmpFacet = null;
			for (i = 1; i < n; i++) {
				if (facetTotal[i - 1].z < facetTotal[i].z) {
					tmpFacet = facetTotal[i];
					facetTotal[i] = facetTotal[i - 1];
					facetTotal[i - 1] = tmpFacet;
				}
			}
			n--;
//System.out.println("Sort pass " + (nFacetTotal - n) + " z min= " + facetTotal[n].z);
		} while (tmpFacet != null);
//System.out.println("---- ---- ---- ---- Sorted " + nFacetTotal + " facets in " + (nFacetTotal - n) + " passes ---- ---- ---- ---- ");
	}

	protected int updateWireframe(int focal, int screenX, int screenY) {
		int i, nEdge;
		nEdge = 0;
		if (parent != null && parent.updatedTransform != null)
			updatedTransform = parent.updatedTransform.mul(transformation);
		else
			updatedTransform = transformation;
		for (i = 0; i < nChild; i++) {
			nEdge += child[i].updateWireframe(focal, screenX, screenY);
		}
		if (mesh.nEdge > 0)
			wireframe = (mesh.transform(updatedTransform)).wireframe(focal,
					screenX, screenY);
		return nEdge + mesh.nEdge;
	}

	protected void updateFacets(int focal, int screenX, int screenY) {
		int i;
		for (i = 0; i < nChild; i++) {
			child[i].updateFacets(focal, screenX, screenY);
		}
		for (i = 0; i < nFacet; i++) {
			facet[i].updatePolygon();
		}
	}

	public void update(int focal, int screenX, int screenY) {
//System.out.println("update " + this.getClass().toString() + " focal=" + focal + " x=" + screenX + " y=" + screenY + " drawMode " + drawMode);
		updateWireframe(focal, screenX, screenY);
		if (drawMode == DRAW_FACETS) {
			updateFacets(focal, screenX, screenY);
			if (facetTotal != null)
				sortFacetTotal();
		}
	}

	public Color colorDelta(Color baseColor, int brightDelta) {
		int rgb, r, g, b;
		rgb = baseColor.getRGB();
		r = ((rgb & 0x00FF0000) >> 16) + brightDelta;
		if (r < 0)
			r = 0;
		if (r > 255)
			r = 255;
		g = ((rgb & 0x0000FF00) >> 8) + brightDelta;
		if (g < 0)
			g = 0;
		if (g > 255)
			g = 255;
		b = ((rgb & 0x000000FF)) + brightDelta;
		if (b < 0)
			b = 0;
		if (b > 255)
			b = 255;
		return new Color(r, g, b);
	}
	
	public void paint(Graphics graphics) {
//System.out.println("Object " + this.getClass() + " paint drawMode=" + drawMode);
		int i;
		brightDelta = -(BRIGHT_SCALE / 2);
		for (i = 0; i < nChild; i++)
			child[i].paint(graphics);
		if (drawMode == DRAW_FACETS && facetTotal != null) {
//System.out.println("Object " + this.getClass() + " DRAW_FACETS paint");
			for (i = 0; i < nFacetTotal; i++) {
				if (BRIGHT_MODE == BRIGHT_SCENE) {
					graphics.setColor(colorDelta(facetTotal[i].obj.color,brightDelta));
					brightDelta += brightIncrement;
				}
				else {
					graphics.setColor(colorDelta(facetTotal[i].obj.color,facetTotal[i].obj.brightDelta));
					facetTotal[i].obj.brightDelta += facetTotal[i].obj.brightIncLocal;
				}
				if (facetTotal[i].z < 0) {
//System.out.println("Object " + this.getClass() + " painted " + i + " / " + nFacetTotal +" facets");
					break;
				}
				facetTotal[i].paint(graphics);
			}
		} else if (drawMode == DRAW_WIREFRAME) {
//System.out.println("Object " + this.getClass() + " DRAW_WIREFRAME paint");
			graphics.setColor(color);
			for (i = 0; i < mesh.nEdge; i++) {
				if (wireframe[i][4] >= 0)
					graphics.drawLine(wireframe[i][0], wireframe[i][1],
						wireframe[i][2], wireframe[i][3]);
			}
		}
	}

	public void setDrawMode(int d) {
		int i;
		drawMode = d;
		if (nFacetTotal == 0 && mesh != null)
			drawMode = DRAW_WIREFRAME;
//System.out.println("Object " + this.getClass() + " setDrawMode " + d + " -> " + drawMode);
		for (i = 0; i < nChild; i++)
			child[i].setDrawMode(d);
	}

}
